/**
 * 	Formatter.java
 *
 *  This file is part of the PDFGen Syllabus Formatter Project.
 *  Copyright (C) 2011  Gian Fontanilla
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.dlsu.pdfgen;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;

import org.dlsu.pdfgen.struct.Syllabus;

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.List;
import com.itextpdf.text.ListItem;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.RomanList;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.draw.VerticalPositionMark;

/**
 * Generates the PDF file from a given <CODE>Syllabus</CODE>.
 *
 * @author Gian Fontanilla
 * @version 0.05a
 * @see Syllabus
 * @since 1.6
 */
public class Formatter {

	/** A font style. */
	public static Font defaultBold = new Font(Font.FontFamily.TIMES_ROMAN,
			12, Font.BOLD);
	
	/** A font style. */
	public static Font defaultNormal = new Font(Font.FontFamily.TIMES_ROMAN,
			12, Font.NORMAL);

	/** A tab marker, first from the left margin. */
	private static final Chunk tab1 = new Chunk(new VerticalPositionMark(),
			100, false);

	/** A tab marker, second from the left margin. */
	private static final Chunk tab2 = new Chunk(new VerticalPositionMark(),
			150, false);

	/** The path to the image. */
	private String imgPath;

	/** The working directory. */
	private String workingDir;

	/** The protocol to generate a <CODE>URL</CODE> object. */
	private String urlProtocol;

	/**
	 * The name of the output file. If not specified, default name is course
	 * code_term_schoolyear.
	 */
	public final String outputFileName;
	
	private Format fontManager;
	/**
	 * Constructs a <CODE>Formatter</CODE> object given a <CODE>Syllabus</CODE>
	 * object and calls <CODE>createDocument()</CODE>.
	 *
	 * @param s
	 *            The <CODE>Syllabus</CODE> object
	 * @throws DocumentException
	 * @throws FileNotFoundException
	 */
	public Formatter(Syllabus s) throws DocumentException,
			FileNotFoundException {

		imgPath = "/src/logo.jpg";
		try {
			workingDir = new File(".").getCanonicalPath();
		} catch (IOException e) {
			e.printStackTrace();
		}
		urlProtocol = "";
		if (System.getProperty("os.name").equals("Linux"))
			urlProtocol = "file://";

		outputFileName = s.getCourseCode() + s.getTerm() + ".pdf";

		createDocument(s);
	}

	/**
	 * Constructs a <CODE>Formatter</CODE> object given a <CODE>Syllabus</CODE>
	 * object and the output file name and calls <CODE>createDocument()</CODE>.
	 *
	 * @param s
	 *            The <CODE>Syllabus</CODE> object
	 * @param fm
	 * 			  The <CODE>FontManager</CODE> object to handle the text formatting           
	 * @param outputFileName
	 *            The specified output file name
	 * @throws DocumentException
	 * @throws FileNotFoundException
	 */
	public Formatter(Syllabus s, Format fm, String outputFileName)
			throws DocumentException, FileNotFoundException {
		
		fontManager = fm;
		imgPath = "/src/logo.jpg";
		try {
			workingDir = new File(".").getCanonicalPath();
		} catch (IOException e) {
			e.printStackTrace();
		}
		urlProtocol = "";
		if (System.getProperty("os.name").equals("Linux"))
			urlProtocol = "file://";

		this.outputFileName = outputFileName;

		createDocument(s);
	}

	/**
	 * Adds an empty line to the document.
	 *
	 * @param doc
	 *            The <CODE>Document</CODE> object where the line should be
	 *            added
	 * @param number
	 *            The number of lines to be added
	 * @throws DocumentException
	 */
	private static void addEmptyLine(Document doc, int number)
			throws DocumentException {

		for (int i = 0; i < number; i++) {
			doc.add(new Paragraph(" "));
		}
	}

	/**
	 * Adds a tab-colon-tab to a <CODE>Paragraph</CODE>.
	 *
	 * @param p
	 *            The paragraph where it is to be added
	 */
	private static void addTabColonTab(Paragraph p) {

		p.add(tab1);
		p.add(":");
		p.add(tab2);
	}

	/**
	 * Creates and returns a numbered <CODE>com.itextpdf.text.List</CODE> object
	 * based on the given <CODE>ArrayList</CODE> object.
	 *
	 * @param items
	 *            The input <CODE>ArrayList</CODE> object
	 * @return The generated list object
	 */
	private static List generateNumberedList(ArrayList<String> items, Font f) {

		List list = new List(true, false, 20);
		for (int i = 0; i < items.size(); i++)
			list.add(new ListItem(new Chunk(items.get(i), f)));

		return list;
	}

	/**
	 * Creates and returns a numbered Roman
	 * <CODE>com.itextpdf.text.RomanList</CODE> object based on the given
	 * <CODE>ArrayList</CODE> object.
	 *
	 * @param items
	 *            The input <CODE>ArrayList</CODE> object
	 * @return The generated list object
	 */
	private static List generateRomanList(ArrayList<String> items, Font f) {

		List list = new RomanList(20);
		for (int i = 0; i < items.size(); i++)
			list.add(new ListItem(new Chunk(items.get(i), f)));

		return list;
	}

	/**
	 * Creates and returns a <CODE>com.itextpdf.text.List</CODE> object based on
	 * the given <CODE>ArrayList</CODE> object.
	 *
	 * @param items
	 *            The input <CODE>ArrayList</CODE> object
	 * @return The generated list object
	 */
	private static List generatePlainList(ArrayList<String> items, Font f) {

		List list = new List(false, false, 10);
		for (int i = 0; i < items.size(); i++)
			list.add(new ListItem(new Chunk(items.get(i), f)));

		return list;
	}

	/**
	 * Creates and returns a bulleted <CODE>com.itextpdf.text.List</CODE> object
	 * based on the given <CODE>ArrayList</CODE> object.
	 *
	 * @param items
	 *            The input <CODE>ArrayList</CODE> object
	 * @return The generated list object
	 */
	private static List generateBulletedList(ArrayList<String> items, Font f) {

		List list = new List(0);
		for (int i = 0; i < items.size(); i++)
			list.add(new ListItem(new Chunk("\u2022  " + items.get(i),
					f)));

		return list;
	}

	/**
	 * Creates a table for the topic outline.
	 *
	 * @return The <CODE>PdfPTable</CODE> object containing the topic outline
	 */
	private static PdfPTable generateTopicOutline(ArrayList list, Font f) {

		float[] colWidth = { 1f, 10f, 1.5f };
		PdfPTable tbl = new PdfPTable(colWidth);
		Font tempFont = Format.clone(f); tempFont.setStyle(Font.BOLD);
		tbl.addCell(new Phrase(new Chunk("Week", tempFont)));
		tbl.addCell(new Phrase(new Chunk("Topic", tempFont)));
		tbl.addCell(new Phrase(new Chunk("Hours", tempFont)));

		for (int i = 0; i < list.size(); i++) {

			tbl.addCell(new Phrase(new Chunk(
					Syllabus.getTopicWeek(list.get(i)), f)));

			tbl.addCell(new Phrase(new Chunk(
					Syllabus.getTopicName(list.get(i)), f)));

			tbl.addCell(new Phrase(new Chunk(
					Syllabus.getTopicHour(list.get(i)), f)));
		}

		return tbl;
	}

	/**
	 * Creates a table for the assessment outline.
	 *
	 * @param list
	 *            <CODE>ArrayList</CODE> object containing the assessments.
	 * @return The <CODE>PdfPTable</CODE> object containing the assessment
	 *         outline
	 */
	private static PdfPTable generateAssessmentOutlineTable(ArrayList list, Font f) {

		float[] colWidth = { 10f, 2f };
		PdfPTable tbl = new PdfPTable(colWidth);
		Font tempFont = Format.clone(f);tempFont.setStyle(Font.BOLD); 
		tbl.addCell(new Phrase(new Chunk("Item", tempFont)));
		tbl.addCell(new Phrase(new Chunk("Value", tempFont)));

		for (int i = 0; i < list.size(); i++) {
			tbl.addCell(new Phrase(new Chunk(Syllabus.getAssessmentItem(list
					.get(i)), f)));
			tbl.addCell(new Phrase(new Chunk(Syllabus.getAssessmentValue(list
					.get(i)), f)));
		}

		return tbl;
	}

	/**
	 * Creates a document given a <CODE>Syllabus</CODE> object. Creates the
	 * necessary paragraphs first and adds all them afterwards in sequence.
	 *
	 * @param s
	 *            The <CODE>Syllabus</CODE> object
	 * @throws FileNotFoundException
	 * @throws DocumentException
	 */
	private void createDocument(Syllabus s) throws FileNotFoundException,
			DocumentException {

		Document doc = new Document();
		PdfWriter.getInstance(doc, new FileOutputStream(outputFileName));

		Image logo = null;
		try {

			logo = Image.getInstance(this.getClass().getResource(
					"resources/logo.jpg"));
			logo.scalePercent(35f);

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (BadElementException e) {
			e.printStackTrace();
		}

		doc.open();
		doc.setPageSize(fontManager.getPageSize());

		// Main heading
		Paragraph mainHeading = new Paragraph(s.getCourseCode() + " SYLLABUS",
				fontManager.getFontBold("code"));
		mainHeading.setAlignment(fontManager.getAlignment("code"));

		// Course title
		Paragraph titleStmt = new Paragraph(s.getCourseCode() + " / "
				+ s.getCourseTitle(), fontManager.getFontBold("title"));
		titleStmt.setAlignment(fontManager.getAlignment("course"));

		// Prerequisite list
		Paragraph preReqStmt = new Paragraph("Prerequisites", fontManager.getFontNormal("prereqs"));
		addTabColonTab(preReqStmt);
		for (int i = 0; i < s.getPreRequisiteCourses().size(); i++)
			preReqStmt.add((i < s.getPreRequisiteCourses().size() - 1) ? s
					.getPreRequisiteCourses().get(i)
					+ ", " : s.getPreRequisiteCourses().get(i));

		// Postrequisite list
		Paragraph postReqStmt = new Paragraph("Prerequisite to", fontManager.getFontNormal("postreqs"));
		addTabColonTab(postReqStmt);
		for (int i = 0; i < s.getPostRequisiteCourses().size(); i++)
			postReqStmt.add((i < s.getPostRequisiteCourses().size() - 1) ? s
					.getPostRequisiteCourses().get(i)
					+ ", " : s.getPostRequisiteCourses().get(i));

		// Course type
		Paragraph typeStmt = new Paragraph("Type of Course", fontManager.getFontNormal("type"));
		addTabColonTab(typeStmt);
		typeStmt.add(s.getCourseType());

		// Faculty
		Paragraph facultyStmt = new Paragraph("Faculty", fontManager.getFontNormal("faculty"));
		addTabColonTab(facultyStmt);
		facultyStmt.add(s.getFaculty());

		// Schedule
		Paragraph scheduleStmt = new Paragraph("Term/Time/Room", fontManager.getFontNormal("schedule"));
		addTabColonTab(scheduleStmt);
		scheduleStmt.add(s.getTerm() + " " + s.getTime() + " " + s.getRoom());

		// Descriptions
		Paragraph descriptionStmt = new Paragraph();
		Font descTemp = fontManager.getFontBold("description");descTemp.setStyle(Font.BOLD);
		descriptionStmt.add(new Chunk("Course Description: ", descTemp));
		descriptionStmt.add(new Chunk(s.getCourseDesc(), fontManager.getFontNormal("description")));

		// Objectives
		Paragraph objectivesStmt = new Paragraph();
		Font objTemp = fontManager.getFontBold("description"); objTemp.setStyle(Font.BOLD);
		objectivesStmt.add(new Chunk("Objectives/Values:\n", objTemp));
		objectivesStmt.add(new Chunk(s.getObjectivePreamble(), fontManager.getFontNormal("objectives")));
		objectivesStmt.add(generateNumberedList(s.getObjectives(), fontManager.getFontNormal("objectives")));

		// Topics
		Paragraph topicsStmt = new Paragraph();
		Font topicTemp = Format.clone(fontManager.getFontBold("topics")); topicTemp.setStyle(Font.BOLD);
		topicsStmt.add(new Chunk("Topics:", topicTemp));
		topicsStmt.add(generateTopicOutline(s.getTopics(), fontManager.getFontNormal("topics")));

		// Requirements
		Paragraph requirementsStmt = new Paragraph();
		Font reqTemp = Format.clone(fontManager.getFontBold("requirements")); reqTemp.setStyle(Font.BOLD);
		requirementsStmt.add(new Chunk("Requirements:", reqTemp));
		if (s.getRequirementListType().equalsIgnoreCase("bulleted"))
			requirementsStmt.add(generateBulletedList(s.getRequirements(),fontManager.getFontNormal("requirements")));
		else if (s.getRequirementListType().equalsIgnoreCase("numbered"))
			requirementsStmt.add(generateNumberedList(s.getRequirements(),fontManager.getFontNormal("requirements")));
		else if (s.getRequirementListType().equalsIgnoreCase("roman"))
			requirementsStmt.add(generateRomanList(s.getRequirements(),fontManager.getFontNormal("requirements")));
		else
			requirementsStmt.add(generatePlainList(s.getRequirements(),fontManager.getFontNormal("requirements")));

		// Assessment
		Paragraph assessmentStmt = new Paragraph();
		Font assTemp = Format.clone(fontManager.getFontBold("assessments")); assTemp.setStyle(Font.BOLD);
		assessmentStmt.add(new Chunk("Assessment/Evaluation:", defaultBold));
		assessmentStmt.add(generateAssessmentOutlineTable(s.getAssessments(),fontManager.getFontNormal("assessments")));

		// Materials
		Paragraph materialsStmt = new Paragraph();
		Font matTemp = Format.clone(fontManager.getDefaultBold()); matTemp.setStyle(Font.BOLD);
		materialsStmt.add(new Chunk("Text/Materials:", matTemp));
		if (s.getMaterialListType().equalsIgnoreCase("bulleted"))
			materialsStmt.add(generateBulletedList(s.getMaterials(), fontManager.getFontNormal("materials")));
		else if (s.getMaterialListType().equalsIgnoreCase("numbered"))
			materialsStmt.add(generateNumberedList(s.getMaterials(),fontManager.getFontNormal("materials")));
		else if (s.getMaterialListType().equalsIgnoreCase("roman"))
			materialsStmt.add(generateRomanList(s.getMaterials(),fontManager.getFontNormal("materials")));
		else
			materialsStmt.add(generatePlainList(s.getMaterials(),fontManager.getFontNormal("materials")));

		// References
		Paragraph referencesStmt = new Paragraph();
		Font refTemp = Format.clone(fontManager.getFontBold("references"));refTemp.setStyle(Font.BOLD);
		referencesStmt.add(new Chunk("References:\n", refTemp));
		if (s.getReferenceListType().equalsIgnoreCase("bulleted"))
			referencesStmt.add(generateBulletedList(s.getReferences(), fontManager.getFontNormal("references")));
		else if (s.getReferenceListType().equalsIgnoreCase("numbered"))
			referencesStmt.add(generateNumberedList(s.getReferences(),fontManager.getFontNormal("references")));
		else if (s.getReferenceListType().equalsIgnoreCase("roman"))
			referencesStmt.add(generateRomanList(s.getReferences(),fontManager.getFontNormal("references")));
		else
			referencesStmt.add(generatePlainList(s.getReferences(), fontManager.getFontNormal("references")));

		// Add to document
		if (logo != null)
			doc.add(logo);
		doc.add(mainHeading);
		addEmptyLine(doc, 2);
		doc.add(titleStmt);
		doc.add(preReqStmt);
		doc.add(postReqStmt);
		doc.add(typeStmt);
		doc.add(facultyStmt);
		doc.add(scheduleStmt);
		addEmptyLine(doc, 1);
		doc.add(descriptionStmt);
		addEmptyLine(doc, 1);
		doc.add(objectivesStmt);
		addEmptyLine(doc, 1);
		doc.add(topicsStmt);
		addEmptyLine(doc, 1);
		addEmptyLine(doc, 1);
		doc.add(requirementsStmt);
		addEmptyLine(doc, 1);
		doc.add(assessmentStmt);
		addEmptyLine(doc, 1);
		doc.add(materialsStmt);
		addEmptyLine(doc, 1);
		doc.add(referencesStmt);

		doc.close();
	}
}
